//
// Lastfm.cs
//
// Copyright (C) 2008 Lukas Lipka <lukaslipka@gmail.com>
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.IO;

using Dashboard.Util;

namespace Lastfm {

	public static class Lastfm {

		private const string AS_WEBSERVICES = "http://ws.audioscrobbler.com/1.0";

		private static string BuildUri (string fragment, params string[] args)
		{			
			return AS_WEBSERVICES + String.Format (fragment, args);
		}

		private static string Encode (string s)
		{
			// Last.FM requires double-encoding of all '/,&?' characters,
			// see http://bugzilla.gnome.org/show_bug.cgi?id=340511

			s = s.Replace ("/", "%2F").Replace (",", "%2C").Replace ("&", "%26").Replace ("?", "%3F");
			return System.Web.HttpUtility.UrlEncode (s);
		}

		public static LastfmUserProfile GetUserProfile (string username)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/user/{0}/profile.xml", username));
			return new LastfmUserProfile (document.SelectSingleNode ("profile"));
		}

		public static LastfmCollection<LastfmUserTopArtist> GetUserTopArtists (string username)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/user/{0}/topartists.xml", username));
			return new LastfmCollection<LastfmUserTopArtist> (document.SelectSingleNode ("topartists"), "artist"); 
		}

		public static LastfmCollection<LastfmUserTopAlbum> GetUserTopAlbums (string username)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/user/{0}/topalbums.xml", username));
			return new LastfmCollection<LastfmUserTopAlbum> (document.SelectSingleNode ("topalbums"), "album"); 
		}

		public static LastfmCollection<LastfmUserTopTrack> GetUserTopTracks (string username)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/user/{0}/toptracks.xml", username));
			return new LastfmCollection<LastfmUserTopTrack> (document.SelectSingleNode ("toptracks"), "track"); 
		}

		public static LastfmCollection<LastfmArtistSimilarArtist> GetArtistSimilarArtists (string artist)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/artist/{0}/similar.xml", Encode (artist)));
			return new LastfmCollection<LastfmArtistSimilarArtist> (document.SelectSingleNode ("similarartists"), "artist"); 
		}

		public static LastfmCollection<LastfmArtistTopAlbum> GetArtistTopAlbums (string artist)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/artist/{0}/topalbums.xml", Encode (artist)));
			return new LastfmCollection<LastfmArtistTopAlbum> (document.SelectSingleNode ("topalbums"), "album"); 
		}

		public static LastfmCollection<LastfmArtistTopTrack> GetArtistTopTracks (string artist)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/artist/{0}/toptracks.xml", Encode (artist)));
			return new LastfmCollection<LastfmArtistTopTrack> (document.SelectSingleNode ("mostknowntracks"), "track"); 
		}

		public static LastfmAlbumInfo GetAlbumInfo (string artist, string album)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("/album/{0}/{1}/info.xml", Encode (artist), Encode (album)));
			return new LastfmAlbumInfo (document.SelectSingleNode ("album"));
		}
	}

	public abstract class LastfmBase {

		private XmlNode root_node = null;

		public LastfmBase (XmlNode root_node, string root_node_name)
		{
			this.root_node = root_node.SelectSingleNode (root_node_name);
		}

		public LastfmBase (XmlNode root_node)
		{
			this.root_node = root_node;
		}

		public T Get<T> (string node)
		{
			if (root_node [node] == null || String.IsNullOrEmpty (root_node [node].InnerText))
				return default (T);

			return (T) Convert.ChangeType (root_node [node].InnerText.Trim (), typeof (T));
		}

		public T GetChild<T> (string node, string child)
		{
			XmlNode parent_node = root_node [node];

			if (parent_node == null || parent_node [child] == null)
				return default (T);
			
			if (String.IsNullOrEmpty (parent_node [child].InnerText))
				return default (T);

			return (T) Convert.ChangeType (parent_node [child].InnerText.Trim (), typeof (T));
		}

		public T GetAttribute<T> (string node, string attribute)
		{
			if (root_node [node] == null || String.IsNullOrEmpty (root_node [node].GetAttribute (attribute)))
				return default (T);

			return (T) Convert.ChangeType (root_node [node].GetAttribute (attribute).Trim (), typeof (T));
		}

		public T GetAttribute<T> (string attribute)
		{
			if (root_node == null || String.IsNullOrEmpty (root_node.Attributes [attribute].InnerText))
				return default (T);

			return (T) Convert.ChangeType (root_node.Attributes [attribute].InnerText.Trim (), typeof (T));
		}

		public LastfmCollection<T> GetCollection<T> (string node, string node_type) where T : LastfmBase
		{
			return new LastfmCollection<T> (root_node [node], node_type);
		}
	}

	public class LastfmCollection<T> : IEnumerable<T> where T : LastfmBase
	{
		private List<T> collection = new List<T> ();

		public LastfmCollection (XmlNode root_node, string root_node_type)
		{
			foreach (XmlNode node in root_node.SelectNodes (root_node_type)) {
				T t = (T) Activator.CreateInstance (typeof (T), node);
				collection.Add (t);
			}
		}

		public IEnumerator<T> GetEnumerator ()
		{
			return collection.GetEnumerator ();
		}

		IEnumerator IEnumerable.GetEnumerator ()
		{
			return collection.GetEnumerator ();
		}
	}

	public class LastfmUserProfile : LastfmBase {
		public LastfmUserProfile (XmlNode node) : base (node) { }

		public string Url { get { return Get<string> ("url"); } }
		public string RealName { get { return Get<string> ("realname"); } }
		public string MBox { get { return Get<string> ("mbox_sha1sum"); } }
		public double Registered { get { return GetAttribute<double> ("registered", "unixtime"); } }
		public uint Age { get { return Get<uint> ("age"); } }
		public char Gender { get { return Get<char> ("gender"); } }
		public string Country { get { return Get<string> ("country"); } }
		public uint PlayCount { get { return Get<uint> ("playcount"); } }
		public string Avatar { get { return Get<string> ("avatar"); } }
		public string Icon { get { return Get<string> ("icon"); } }
	}

	public class LastfmUserTopArtist : LastfmBase {
		public LastfmUserTopArtist (XmlNode node) : base (node) { }

		public string Name { get { return Get<string> ("name"); } }
		public string Mbid { get { return Get<string> ("mbid"); } }
		public uint PlayCount { get { return Get<uint> ("playcount"); } }
		public uint Rank { get { return Get<uint> ("rank"); } }
		public string Url { get { return Get<string> ("url"); } }
		public string Thumbnail { get { return Get<string> ("thumbnail"); } }
		public string Image { get { return Get<string> ("image"); } }
	}

	public class LastfmUserTopAlbum : LastfmBase {
		public LastfmUserTopAlbum (XmlNode node) : base (node) { }

		public string Artist { get { return Get<string> ("artist"); } }
		public string Name { get { return Get<string> ("name"); } }
		public string Mbid { get { return Get<string> ("mbid"); } }
		public uint PlayCount { get { return Get<uint> ("playcount"); } }
		public uint Rank { get { return Get<uint> ("rank"); } }
		public string Url { get { return Get<string> ("url"); } }
		public string ImageLarge { get { return GetChild<string> ("image", "large"); } }
		public string ImageMedium { get { return GetChild<string> ("image", "medium"); } }
		public string ImageSmall { get { return GetChild<string> ("image", "small"); } }
	}

	public class LastfmUserTopTrack : LastfmBase {
		public LastfmUserTopTrack (XmlNode node) : base (node) { }

		public string Artist { get { return Get<string> ("artist"); } }
		public string Name { get { return Get<string> ("name"); } }
		public string Mbid { get { return Get<string> ("mbid"); } }
		public uint PlayCount { get { return Get<uint> ("playcount"); } }
		public uint Rank { get { return Get<uint> ("rank"); } }
		public string Url { get { return Get<string> ("url"); } }
	}

	public class LastfmArtistSimilarArtist : LastfmBase {
		public LastfmArtistSimilarArtist (XmlNode node) : base (node) { }
		
		public string Name { get { return Get<string> ("name"); } }
		public string Mbid { get { return Get<string> ("mbid"); } }
		public double Match { get { return Get<double> ("match"); } }
		public string Url { get { return Get<string> ("url"); } }
		public string Image { get { return Get<string> ("image"); } }
		public string ImageSmall { get { return Get<string> ("image_small"); } }
		public uint Streamable { get { return Get<uint> ("streamable"); } }
	}

	public class LastfmArtistTopAlbum : LastfmBase {
		public LastfmArtistTopAlbum (XmlNode node) : base (node) { }

		public string Name { get { return Get<string> ("name"); } }
		public string Mbid { get { return Get<string> ("mbid"); } }
		public uint Reach { get { return Get<uint> ("reach"); } }
		public string Url { get { return Get<string> ("url"); } }
		public string ImageLarge { get { return GetChild<string> ("image", "large"); } }
		public string ImageMedium { get { return GetChild<string> ("image", "medium"); } }
		public string ImageSmall { get { return GetChild<string> ("image", "small"); } }
	}

	public class LastfmArtistTopTrack : LastfmBase {
		public LastfmArtistTopTrack (XmlNode node) : base (node) { }

		public string Name { get { return Get<string> ("name"); } }
		public string Mbid { get { return Get<string> ("mbid"); } }
		public uint Reach { get { return Get<uint> ("reach"); } }
		public string Url { get { return Get<string> ("url"); } }
	}

	public class LastfmAlbumInfo : LastfmBase {
		public LastfmAlbumInfo (XmlNode node) : base (node) { }

		public uint Reach { get { return Get<uint> ("reach"); } }
		public string Url { get { return Get<string> ("url"); } }
		public string ReleaseDate { get { return Get<string> ("releasedate"); } }
		public string Mbid { get { return Get<string> ("mbid"); } }
		public string ImageLarge { get { return GetChild<string> ("coverart", "large"); } }
		public string ImageMedium { get { return GetChild<string> ("coverart", "medium"); } }
		public string ImageSmall { get { return GetChild<string> ("coverart", "small"); } }
		public LastfmCollection<LastfmAlbumInfoTrack> Tracks { get { return GetCollection<LastfmAlbumInfoTrack> ("tracks", "track"); } }
	}

	public class LastfmAlbumInfoTrack : LastfmBase {
		public LastfmAlbumInfoTrack (XmlNode node) : base (node) { }

		public string Title { get { return GetAttribute<string> ("title"); } }
		public uint Reach { get { return Get<uint> ("reach"); } }
		public string Url { get { return Get<string> ("url"); } }
	}
}
